;;;
;;; JaCoLiB
;;; Copyright (C) 2008 Alessandro Serra
;;; 
;;; This program is free software: you can redistribute it and/or modify
;;; it under the terms of the GNU General Public License as published by
;;; the Free Software Foundation, either version 3 of the License, or
;;; (at your option) any later version.
;;; 
;;; This program is distributed in the hope that it will be useful,
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;; 
;;; You should have received a copy of the GNU General Public License
;;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
;;;

;;;; Created on 2008-07-01 08:52:11

(in-package :jacolib)

;
; connectors (client or server)
;

; parameters

(defparameter *default-local-server-port* 9608)
(defparameter *default-remote-server-port* 9609)
(defparameter *default-secret-string* "********")
(defparameter *default-authorization-type* :plain)
(defparameter *default-host* "localhost")
(defparameter *default-server-host* #(0 0 0 0))
(defparameter *default-connection-maker* #'(lambda (socket) (make-connection socket)))
(defparameter *default-server-connection-handler* #'(lambda (connection) (connection-close connection)))


;
; connector
;

(defclass connector ()
  ((port 
    :TYPE integer 
    :INITARG :port 
    :READER connector-port)
   (authorization-type
    :TYPE symbol
    :INITARG :authorization-type
    :INITFORM *default-authorization-type*
    :READER connector-authorization-type
    :documentation "Authorization type is one of md5, plain, none")
   (secret-string 
    :TYPE string 
    :INITARG :secret-string 
    :INITFORM *default-secret-string* 
    :READER connector-secret-string)
   (connection-maker 
    :INITARG :connection-maker
    :READER connector-connection-maker
    :INITFORM *default-connection-maker*))
  (:documentation "An abstract class for all connector (object used to make a new connection between two peers"))


;
; client connectors
;

(defclass client-connector (connector)
  ((port 
    :INITFORM *default-remote-server-port*)
   (host
    :INITARG :host
    :INITFORM *default-host*
    :READER client-connector-host))
  (:documentation "A connector is used to establish a new connection with a server"))

(defun client-connector-connect (connector)
  (declare (type client-connector connector))
  (let ((socket (usocket:socket-connect (client-connector-host connector) (connector-port connector))))
    (if (not (authenticate-connection (usocket:socket-stream socket) (connector-secret-string connector)))
        (error 'protocol-error :message "authorization failure")
        (let ((connection (funcall (connector-connection-maker connector) socket)))
          connection))))


;
; server connectors
;

(defclass server-connector (connector)
  ((port 
    :INITFORM *default-local-server-port*)
   (host 
    :INITARG :host
    :INITFORM *default-server-host*
    :READER server-connector-host)
   (connection-handler 
    :INITARG :connection-handler
    :READER connector-connection-handler
    :INITFORM *default-server-connection-handler*)
   (use-multi-threadp 
    :INITARG :use-multi-threadp
    :INITFORM nil
    :READER server-connector-use-multi-threadp)
   (main-thread
    :INITFORM nil
    :ACCESSOR server-connector-main-thread)
   (socket
    :READER server-connector-socket)))

(defmethod initialize-instance :after ((connector server-connector) &rest initargs)
  (declare (ignore initargs))
  (progn
    (format t "~a ~a:~a~%" "lisp server listening on port "  (server-connector-host connector) (connector-port connector))
    (setf (slot-value connector 'socket) 
          (usocket:socket-listen (server-connector-host connector) (connector-port connector) :reuse-address t))))

(defun server-connector-quit (connector)
  (declare (type server-connector connector))
  (format t "~a~%" "server closed")
  (usocket::socket-close (server-connector-socket connector))
  (setf (slot-value connector 'socket) nil)
  #-abcl (if (server-connector-use-multi-threadp connector)
             (portable-threads:kill-thread (server-connector-main-thread connector))))

(defun server-connector-handle-connection (connector socket)
  (handler-case
   (if (ask-connection-authentication (usocket:socket-stream socket) (connector-authorization-type connector) (connector-secret-string connector))
       (let ((connection (funcall (connector-connection-maker connector) socket)))
         (format t "~a~%" "client connected")
         (funcall (connector-connection-handler connector) connection)
         (format t "~a~%" "client disconnected"))
       (progn
         (format t "~a~%" "client authorization fails")
         (usocket:socket-close socket)))
   (condition (condition) 
     (progn 
       (format t "[connection-handler]: ~a ~%" condition)
       nil))))
   
(defun server-connector-run (connector)
  (declare (type server-connector connector))
  #-abcl (if (server-connector-use-multi-threadp connector)
             (setf (server-connector-main-thread connector) (portable-threads:current-thread))) 
  (let ((i 0))
    (loop
     (if (null (server-connector-socket connector))
         (return)
         (let ((socket (usocket:socket-accept (server-connector-socket connector))))
           (if (usocket:socket socket)
               (if (server-connector-use-multi-threadp connector)
                   #-abcl (portable-threads:spawn-thread (format nil "th-~A" i) 
                                                  #'(lambda (connector socket)
                                                      (server-connector-handle-connection connector socket))
                                                  connector
                                                  socket)
                   #+abcl (server-connector-handle-connection connector socket)
                   (server-connector-handle-connection connector socket)))))
     (setf i (+ i 1)))))
